package com.atstudent.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.atstudent.gmall.common.constant.GmallConstant;
import com.atstudent.gmall.gateway.properties.UserAuthProperties;
import com.atstudent.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * @Author 薛有朋
 * @CreatDate 2025/3/29 19:00
 **/
@Component
@Slf4j
public class UserAuthFilter implements GlobalFilter , Ordered {

    @Autowired
    private UserAuthProperties userAuthProperties;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate<String , String> redisTemplate;

    /**
     * 拦截所有请求
     * @Author 薛有朋
     * @Date 2025/3/29 19:02
     **/
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String path = serverHttpRequest.getURI().getPath();
        log.info("UserAuthFilter...filter方法执行了... , path: {}" , path);

        // 不需要验证用户登录的路径规则的处理
        List<String> noauthurls = userAuthProperties.getNoauthurls();
        for (String noauthurl : noauthurls){
            //第一个参数是定义的规则 第二个是我们的路径
            boolean match = antPathMatcher.match(noauthurl, path);
            //满足规则 直接放行
            if (match){
                return chain.filter(exchange);
            }
        }

        //需要验证用户登陆的路径规则的处理
        List<String> authurls = userAuthProperties.getAuthurls();
        for (String authurl : authurls){
            boolean match = antPathMatcher.match(authurl, path);
            if (match){
                //获取token
                String token = getToken(serverHttpRequest);
                //看是否有token
                if (StringUtils.isEmpty(token)){ //token为空，未登录，把用户重定向到登录页面
                    return locationUrl(exchange,userAuthProperties.getToLoginPage());
                }else { //token不为空 校验token的合法性
                    //根据token获取Redis中保存的用户登陆信息
                    UserInfo userInfo = getUserInfoByToken(token);
                    if (userInfo == null){ //token违法  重定向到登录页面
                        return locationUrl(exchange,userAuthProperties.getToLoginPage());
                    }else { //token合法 正常放行
                        return userIdThrought(exchange, chain , userInfo);
                    }
                }
            }
        }

        //普通资源： 就是用户在登录和未登录状态下都可以进行访问的资源
        String token = getToken(serverHttpRequest);
        if (StringUtils.isEmpty(token)){ //用户未登录 直接放行
            return userTempIdThrought(exchange , chain);
        }else { //校验token的合法性
            UserInfo userInfo = getUserInfoByToken(token);
            if (userInfo == null){ //伪造token ，踢回登录页面
                return locationUrl(exchange,userAuthProperties.getToLoginPage());
            }else { // 用户登录放行,在放行之前需要进行用户id的透传
                return userIdThrought(exchange, chain , userInfo);
            }
        }

    }

    /**
     * 临时用户的id透传
     * @Author 薛有朋
     * @Date 2025/4/7 18:52
     **/
    private Mono<Void> userTempIdThrought(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取临时用户的id
        String userTempId = getUserTempId(exchange);

        //创建一个新的Request对象  mutate方法获取一个建造者对象
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header("userTempId", userTempId).build();
        //创建一个新的exchange对象 用来封装新的Request对象
        ServerWebExchange webExchange = exchange.mutate().request(serverHttpRequest).response(exchange.getResponse()).build();

        return chain.filter(webExchange);
    }

    /**
     * 透传的思路：创建一个新的Request对象，然后在该对象的请求头中添加一个新的请求头userId , 在放行的时候使用新的request对象
     * 涉及到购物车合并，所以用户登陆时透传id也要把临时用户的id进行透传
     * @Author 薛有朋
     * @Date 2025/4/3 19:55
     **/
    private Mono<Void> userIdThrought(ServerWebExchange exchange, GatewayFilterChain chain, UserInfo userInfo) {

        //获取临时用户的id
        String userTempId = getUserTempId(exchange) ;

        //创建一个新的Request对象  mutate方法获取一个建造者对象
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
                .header("userId", String.valueOf(userInfo.getId()))
                .header("userTempId" , userTempId)
                .build();
        // 创建一个新的exchange对象 用来封装新的Request对象
        ServerWebExchange webExchange = exchange.mutate().request(serverHttpRequest).response(exchange.getResponse()).build();
        //放行
        return chain.filter(webExchange);
    }

    // 把用户踢回到登录页面
    private Mono<Void> locationUrl(ServerWebExchange exchange, String toLoginPage) {
        //获取访问当前资源的请求路径
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String originUrl = serverHttpRequest.getURI().toString();

        // 进行请求重定向 ====> 302状态码 + location响应头
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.setStatusCode(HttpStatus.FOUND);
        serverHttpResponse.getHeaders().add("location" , toLoginPage + "?originUrl=" + originUrl);

        //给serverHttpResponse添加新的Cookie 因为需要到登录页面的要么是没有token的 要么是token有问题的 所以重新设置Cookie
        ResponseCookie tokenCookie = ResponseCookie.from("token", "").domain("gmall.com").path("/").maxAge(-1).build();
        ResponseCookie userInfoCookie = ResponseCookie.from("userInfo", "").domain("gmall.com").path("/").maxAge(-1).build();
        serverHttpResponse.addCookie(tokenCookie);
        serverHttpResponse.addCookie(userInfoCookie);

        // 不进行放行，结束请求
        return serverHttpResponse.setComplete();
    }

    private UserInfo getUserInfoByToken(String token) {
        String userInfoJson = redisTemplate.opsForValue().get(GmallConstant.REDIS_USER_LOGIN_PRE + token);
        if (StringUtils.isEmpty(userInfoJson)){ //如果没有查到用户信息 说明token不合法 返回null
            return null;
        }else {
            UserInfo userInfo = JSON.parseObject(userInfoJson, UserInfo.class);
            return userInfo;
        }
    }

    /**
     *  获取token
     *  前端传递token的方式：
     *  1、通过请求头(token)进行传递
     *  2、通过Cookie进行传递的
     */
    private String getToken(ServerHttpRequest serverHttpRequest) {
        //先获取到cookie中的token
        HttpCookie httpCookie = serverHttpRequest.getCookies().getFirst("token");
        if (httpCookie != null){
            String token = httpCookie.getValue(); //获取token的值
            return token;
        }else { //获取请求头中的token
            String token = serverHttpRequest.getHeaders().getFirst("token");
            return token;
        }
    }

    /**
     * 获取临时用户的id
     * @Author 薛有朋
     * @Date 2025/4/7 18:05
     **/
    private String getUserTempId(ServerWebExchange exchange) {
        //先从cookie中获取
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpCookie httpCookie = serverHttpRequest.getCookies().getFirst("userTempId");
        if (httpCookie != null){
            return httpCookie.getValue();
        }else { //再从请求头中获取
            String userTempId = serverHttpRequest.getHeaders().getFirst("userTempId");
            return userTempId;
        }
    }


    /**
     * 定义过滤器的编号，后期框架会调用该方法获取当前过滤器的编号，然后根据这个编号对过滤器进行排序，后期就按照排序以后的结果依次执行对应的过滤器
     * @Author 薛有朋
     * @Date 2025/3/29 19:01
     **/
    @Override
    public int getOrder() {
        return -1;
    }
}
